package com.tsd.core.job;

import cn.hutool.db.Entity;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.tsd.core.datasource.DataSourceContextHolder;
import com.tsd.core.datasource.DataSourceUtils;
import com.tsd.core.utils.ExceptionUtil;
import com.tsd.core.utils.HlpUtils;
import com.tsd.core.utils.ListUtil;
import com.tsd.core.vo.PageBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;

/**
 * 定时任务控制类的基类
 * 各个任务只需要继承本基类，然后定义方法，在方法内调用fetchAccountSet进行多账套的处理，并提供callback方法，
 * 在callback的dealBody中编写任务处理逻辑。
 * 需要作为Task的类可以通过注解@TaskJob(name = "XXXX")进行声明，方法则通过@TaskJobMethod(name = "YYYYY")声明
 * 通过注解声明的类和方法就可以在后台系统的配置备选中进行显示以便选择
 *
 * @author wudw
 */
public class JobBaseClass {
    private static final Logger LOGGER = LoggerFactory.getLogger("quartzLogger");

    public interface CallBack {
        //针对某个账套进行处理
//		void dealWith(SysAccountSetExt sysAccountSetExt);
        //处理主体，返回处理记录数量
        int dealBody();

        //整体执行完毕
        void complete();
    }

    private final BlockingQueue<String> priorityQueue = new LinkedBlockingDeque<>();

    public void addPpriorityQueue(String accoutSetSid) {
        if (!priorityQueue.contains(accoutSetSid)) {
            try {
                priorityQueue.put(accoutSetSid);
            } catch (InterruptedException e) {
                LOGGER.error(ExceptionUtil.getStackTraceFromException(e));
            }
        }
    }

    private String takeNext() {
        try {
            return this.priorityQueue.take();
        } catch (InterruptedException e) {
            LOGGER.error(ExceptionUtil.getStackTraceFromException(e));
        }
        return null;
    }

    /**
     * 检查账套是否拥有模块
     *
     * @param accountSetExt
     * @param className
     * @param methodName
     * @param jobExtList
     * @return
     */
    private boolean checkMode(Entity accountSetExt, String className, String methodName, List<Entity> jobExtList) {
        String[] strings = className.split("\\.");
        String beanName = HlpUtils.toLowerCaseFirstOne(strings[strings.length - 1]);
        // 1、判断任务是否存在
        Entity jobExt = ListUtil.get(jobExtList, t -> t.getStr("job_object_name").equals(beanName) && t.getStr("job_method_name").equals(methodName));
        if (jobExt == null) {
            LOGGER.info("系统未定义任务，账套:{},{}.{}", accountSetExt.getStr("code"), className, methodName);
            return false;
        }
        // 2、判断账套是否拥有模块
        String modeStr = accountSetExt.getStr("mode");
        List<String> modeList = new ArrayList<>();
        if (!HlpUtils.isEmpty(modeStr) && JSONUtil.isJsonArray(modeStr)) {
            modeList = JSON.parseArray(modeStr, String.class);
        }
        boolean hasMode = ListUtil.anyMatch(modeList, t -> jobExt.getStr("mode").equals(t));
        if (!hasMode) {
            LOGGER.info("账套：{}，不存在模块：{}，不执行", accountSetExt.getStr("code"), jobExt.getStr("mode"));
        }
        return hasMode;
    }

    /**
     * 获取账套，并调用callback方法
     *
     * @param tag        只是用于写日志
     * @param className  只是用于写日志
     * @param methodName 只是用于写日志
     * @param callBack   任务回调，定时任务需要执行的逻辑写在callback的dealBody内
     */
    public void fetchAccountSet(String tag, String className, String methodName, CallBack callBack) {
        if (callBack == null) {
            LOGGER.info(tag + ",callBack 为空，不执行");
            return;
        }
        LOGGER.info("查询系统定义的任务列表...");
        DataSourceContextHolder.setDataSourceType(DataSourceContextHolder.DEFAULT_ACCOUNT_SET_SID);
        List<Entity> jobExtList = JobUtils.getTaskJobList();
        if (HlpUtils.isEmptyList(jobExtList)) {
            LOGGER.info("系统未定义任务列表，不执行");
            return;
        }
        if (!this.priorityQueue.isEmpty()) {
            LOGGER.info(tag + "开始：执行优先队列..." + tag);
            while (!this.priorityQueue.isEmpty()) {
                String accountSid = this.takeNext();
                if (!HlpUtils.isEmpty(accountSid)) {
                    try {
                        Entity accountSetExt = DataSourceUtils.getAccountSetBySid(DataSourceContextHolder.getMasterDataSource(), accountSid);
                        if (accountSetExt != null) {
                            // 检查账套是否拥有模块
                            boolean checkMode = this.checkMode(accountSetExt, className, methodName, jobExtList);
                            if (!checkMode) {
                                continue;
                            }
                            this.dealWithLog(accountSetExt, callBack, tag, className, methodName);
                        }
                    } catch (Exception e) {
                        String trace = ExceptionUtil.getStackTraceFromException(e);
                        LOGGER.error("定时任务，获取账套出错,params=" + accountSid);
                        LOGGER.error(trace);
                    }
                }
            }
            LOGGER.info(tag + "结束：执行优先队列..." + tag);
            callBack.complete();
            return;
        }
        LOGGER.info(tag + "开始：fetchAccountSet..." + tag);
        Map<String, Object> params = new HashMap<>(2);
        params.put("dr", 0);
        params.put("state", 2);
        int pageIndex = 1;
        int pageSize = 100;
        try {
            while (true) {
                PageBean page = new PageBean(pageIndex, pageSize);
                DataSourceContextHolder.setDataSourceType(DataSourceContextHolder.DEFAULT_ACCOUNT_SET_SID);
                List<Entity> list;
                try {
                    list = DataSourceUtils.getValidAccountSetList(DataSourceContextHolder.getMasterDataSource());
                } catch (Exception e) {
                    String trace = ExceptionUtil.getStackTraceFromException(e);
                    LOGGER.error("定时任务，获取账套列表出错,params=" + JSON.toJSONString(params) + " page:" + JSON.toJSONString(page));
                    LOGGER.error(trace);
                    break;
                }
                for (Entity accountSetExt : list) {
                    // 检查账套是否拥有模块
                    boolean checkMode = this.checkMode(accountSetExt, className, methodName, jobExtList);
                    if (!checkMode) {
                        continue;
                    }
                    this.dealWithLog(accountSetExt, callBack, tag, className, methodName);
                }
                if (list.size() < pageSize) {
                    break;
                } else {
                    pageIndex++;
                }
            }
        } catch (Exception e) {
            String trace = ExceptionUtil.getStackTraceFromException(e);
            LOGGER.error("定时处理，处理账套内记录时出错");
            LOGGER.error(trace);
        }
        LOGGER.info(tag + "结束：fetchAccountSet..." + tag);
        callBack.complete();
    }

    /**
     * 带有日志记录的处理过程
     *
     * @param accountSetExt
     * @param callBack
     * @param log_tag
     * @param className
     * @param methodName
     */
    protected void dealWithLog(Entity accountSetExt, CallBack callBack, String log_tag, String className, String methodName) {
        String sid = accountSetExt.getStr("sid");
        DataSourceContextHolder.setDataSourceType(sid);
        LOGGER.info("开始任务:" + sid + "," + log_tag);
        Entity logEntity = new Entity();
        logEntity.put("job_object_name", className);
        logEntity.put("job_method_name", methodName);
        logEntity.put("update_date", new Date());
        logEntity.put("owner_sid", sid);
        int qty = 0;
        int code = 0;
        try {
            if (callBack != null) {
                qty = callBack.dealBody();
            }
            logEntity.put("exec_code", code);
            logEntity.put("ext0", BigDecimal.valueOf(qty));
        } catch (Exception e) {
            code = -1;
            logEntity.put("exec_code", code);
            logEntity.put("ext0", BigDecimal.valueOf(qty));
            logEntity.put("exec_msg", e.getMessage());
            logEntity.put("exec_trace", ExceptionUtil.getStackTraceFromException(e));
        }
        JobUtils.saveJobExecLog(DataSourceContextHolder.getMasterDataSource(), logEntity);
        LOGGER.info("结束任务：" + sid + "," + log_tag);
    }
}
